Skip to content

meta: initial VM tests setup#551

Open
NotAShelf wants to merge 3 commits intomasterfrom
vm-test-stuff
Open

meta: initial VM tests setup#551
NotAShelf wants to merge 3 commits intomasterfrom
vm-test-stuff

Conversation

@NotAShelf
Copy link
Copy Markdown
Member

@NotAShelf NotAShelf commented Feb 3, 2026

Supersedes #516

we test those

Summary by CodeRabbit

  • Tests

    • Added comprehensive remote build-and-deploy tests that exercise local and remote build flows across multiple systems and verify activation and failure cases.
    • Introduced reusable test SSH keys to support integration scenarios.
  • Chores

    • Clarified CI job names to include target architectures and added an extra Linux CI step for cross-system testing.

Partially borrowed from Nixpkgs

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icae2926727255621cc1178488f7b65f06a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4fc35496a845d9a16179a20b831a22526a6a6964
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 3, 2026

Walkthrough

Adds a cross-system NixOS VM test (nh-remote-test), test SSH keys, and CI changes to run the new test; flake.nix is updated to expose per-system nh-remote-test checks for multiple platforms.

Changes

Cohort / File(s) Summary
CI Workflow
.github/workflows/test.yaml
Rename job display names to include architecture (x86_64-linux, aarch64-darwin) and add a step to run nix build .#checks.x86_64-linux.nh-remote-test.
Flake configuration
flake.nix
Extend checks to merge per-system attributes via genAttrs, exposing a nh-remote-test derivation for each system using callPackage ./test/vm/test-remote.nix with nh and nixpkgs.
Test assets
test/ssh-keys.nix
Add snakeOil test SSH key pair: snakeOilPrivateKey (PEM EC private key via pkgs.writeText) and snakeOilPublicKey (SSH public key string).
VM test
test/vm/test-remote.nix
Add nixosTest nh-remote-test that provisions deployer, target, and buildHost VMs, configures SSH keys, orchestrates remote/local builds and activations, and performs multiple positive/negative subtests and assertions.

Sequence Diagram(s)

sequenceDiagram
  participant CI as CI
  participant Deployer as Deployer VM
  participant BuildHost as BuildHost VM
  participant Target as Target VM

  CI->>Deployer: start nh-remote-test (nixosTest)
  Deployer->>Target: provision SSH keys & known_hosts
  Deployer->>BuildHost: provision SSH keys & known_hosts
  Note right of Deployer: generate hardware config, transfer files
  Deployer->>Target: scp hardware-configuration.nix + configs
  Deployer->>Target: ssh run local build & activation
  Deployer->>BuildHost: trigger remote build (--build-host)
  BuildHost->>Target: build artefacts transferred or targeted deployment
  Target->>Deployer: report activation status / hostname checks
  Deployer->>CI: test results (pass/fail)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • faukah
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'meta: initial VM tests setup' accurately summarizes the main objective of the PR, which introduces initial NixOS VM test infrastructure including test files, SSH keys, CI configuration, and a comprehensive test workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vm-test-stuff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@flake.nix`:
- Around line 29-37: The current checks assignment uses the shallow merge
operator (//) which causes self.packages.${system} to be overwritten by the
genAttrs result and drops Linux package checks; update the merge to perform a
deep/recursive merge so nested keys are preserved — for example use
nixpkgs.lib.recursiveUpdate (or nixpkgs.lib.recursiveUpdateAttrs) to combine
self.packages and the genAttrs result that defines nh-remote-test, or refactor
to forAllSystems with conditional logic that adds nh-remote-test only for
non-Darwin systems; locate the checks assignment and replace the top-level //
merge of self.packages and the genAttrs block (the nh-remote-test definition)
with a recursiveUpdate-based merge or a forAllSystems conditional so nh and
default entries in self.packages.${system} are not lost.

In `@test/vm/test-remote.nix`:
- Around line 209-224: The remote subtests call deployer.succeed("nh os switch
... -f '<nixpkgs/nixos>'") but never create /root/configuration.nix on the
deployer; copy the appropriate prepared file to /root/configuration.nix on the
deployer before each remote deployer.succeed invocation (e.g. copy
configuration-2.nix before the subtest that expects config-2-deployed and
configuration-3.nix before the subtest that expects config-3-deployed). Use
deployer.succeed to perform the copy on the deployer host immediately prior to
the deployer.succeed("nh os switch ...") calls so the switch command finds
/root/configuration.nix.
🧹 Nitpick comments (2)
.github/workflows/test.yaml (1)

6-24: Workflow paths filter may be incomplete.

The paths filter references .github/workflows/build.yaml (line 13, 24) but this file is test.yaml. Additionally, changes to test files (test/**) won't trigger this workflow, which means modifications to test/vm/test-remote.nix or test/ssh-keys.nix won't automatically run the tests.

Consider updating the paths filter:

Suggested paths additions
     paths:
       - "Cargo.toml"
       - "Cargo.lock"
       - "xtask/Cargo.toml"
       - "src/**"
       - "xtask/src/**"
       - ".cargo/**"
-      - ".github/workflows/build.yaml"
+      - ".github/workflows/test.yaml"
+      - "test/**"

Apply to both pull_request and push sections.

test/vm/test-remote.nix (1)

13-16: Consider documenting the security tradeoff in the SSH config.

The SSH config disables host key verification, which is appropriate for ephemeral test VMs. However, the test also populates known_hosts (lines 176-177), making these settings partially redundant. Consider either:

  1. Removing known_hosts population since verification is disabled, or
  2. Removing the StrictHostKeyChecking=no since known_hosts is populated

The current approach works but is slightly inconsistent.

Comment thread flake.nix
Comment on lines +29 to +37
checks =
self.packages
// self.devShells
// nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
inherit (self.packages.${system}) nh;
inherit nixpkgs;
};
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Incorrect attribute set merge overwrites checks for Linux systems.

The // operator performs shallow merging at the top level (system keys), not a deep merge of the inner attributes. This means:

  • self.packages.x86_64-linux (containing nh, default) gets overwritten by the genAttrs result { nh-remote-test = ...; }
  • Linux systems lose their package checks entirely
  • Darwin systems won't have nh-remote-test (correct) but the merge still has issues

To properly merge nested attrsets, use lib.recursiveUpdate or restructure:

Proposed fix using recursiveUpdate
       checks =
-        self.packages
-        // self.devShells
-        // nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
-          nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
-            inherit (self.packages.${system}) nh;
-            inherit nixpkgs;
-          };
-        });
+        nixpkgs.lib.recursiveUpdate
+          (nixpkgs.lib.recursiveUpdate self.packages self.devShells)
+          (nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
+            nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
+              inherit (self.packages.${system}) nh;
+              inherit nixpkgs;
+            };
+          }));

Alternatively, use forAllSystems with conditional logic for a cleaner approach.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checks =
self.packages
// self.devShells
// nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
inherit (self.packages.${system}) nh;
inherit nixpkgs;
};
});
checks =
nixpkgs.lib.recursiveUpdate
(nixpkgs.lib.recursiveUpdate self.packages self.devShells)
(nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
inherit (self.packages.${system}) nh;
inherit nixpkgs;
};
}));
🤖 Prompt for AI Agents
In `@flake.nix` around lines 29 - 37, The current checks assignment uses the
shallow merge operator (//) which causes self.packages.${system} to be
overwritten by the genAttrs result and drops Linux package checks; update the
merge to perform a deep/recursive merge so nested keys are preserved — for
example use nixpkgs.lib.recursiveUpdate (or nixpkgs.lib.recursiveUpdateAttrs) to
combine self.packages and the genAttrs result that defines nh-remote-test, or
refactor to forAllSystems with conditional logic that adds nh-remote-test only
for non-Darwin systems; locate the checks assignment and replace the top-level
// merge of self.packages and the genAttrs block (the nh-remote-test definition)
with a recursiveUpdate-based merge or a forAllSystems conditional so nh and
default entries in self.packages.${system} are not lost.

Comment thread test/vm/test-remote.nix
Comment on lines +209 to +224
with subtest("Remote build on deployer, deploy to target with --target-host"):
deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")

# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-2-deployed", f"Expected 'config-2-deployed', got '{target_hostname}'"

# Build on buildHost, activate on target (both different from deployer)
with subtest("Remote build on buildHost with --build-host, deploy to target with --target-host"):
deployer.succeed(
f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --build-host root@{build_host_ip} --target-host root@{target_ip}"
)

# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-3-deployed", f"Expected 'config-3-deployed', got '{target_hostname}'"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing configuration file setup for remote subtests.

The remote build subtests (lines 209-248) run nh os switch with -f '<nixpkgs/nixos>' on the deployer, which expects /root/configuration.nix to exist. However, only /root/configuration-{1,2,3}.nix files are copied to the deployer (lines 189-191), and /root/configuration.nix is never created on the deployer.

Each remote subtest should copy the appropriate configuration file to /root/configuration.nix on the deployer before running the switch command:

Proposed fix for the second subtest
     # Build on deployer, activate on target
     with subtest("Remote build on deployer, deploy to target with --target-host"):
+        deployer.succeed("cp /root/configuration-2.nix /root/configuration.nix")
         deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")

Similar changes needed for the other remote subtests with the appropriate configuration file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with subtest("Remote build on deployer, deploy to target with --target-host"):
deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-2-deployed", f"Expected 'config-2-deployed', got '{target_hostname}'"
# Build on buildHost, activate on target (both different from deployer)
with subtest("Remote build on buildHost with --build-host, deploy to target with --target-host"):
deployer.succeed(
f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --build-host root@{build_host_ip} --target-host root@{target_ip}"
)
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-3-deployed", f"Expected 'config-3-deployed', got '{target_hostname}'"
with subtest("Remote build on deployer, deploy to target with --target-host"):
deployer.succeed("cp /root/configuration-2.nix /root/configuration.nix")
deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-2-deployed", f"Expected 'config-2-deployed', got '{target_hostname}'"
# Build on buildHost, activate on target (both different from deployer)
with subtest("Remote build on buildHost with --build-host, deploy to target with --target-host"):
deployer.succeed(
f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --build-host root@{build_host_ip} --target-host root@{target_ip}"
)
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-3-deployed", f"Expected 'config-3-deployed', got '{target_hostname}'"
🤖 Prompt for AI Agents
In `@test/vm/test-remote.nix` around lines 209 - 224, The remote subtests call
deployer.succeed("nh os switch ... -f '<nixpkgs/nixos>'") but never create
/root/configuration.nix on the deployer; copy the appropriate prepared file to
/root/configuration.nix on the deployer before each remote deployer.succeed
invocation (e.g. copy configuration-2.nix before the subtest that expects
config-2-deployed and configuration-3.nix before the subtest that expects
config-3-deployed). Use deployer.succeed to perform the copy on the deployer
host immediately prior to the deployer.succeed("nh os switch ...") calls so the
switch command finds /root/configuration.nix.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/test.yaml:
- Around line 49-51: The workflow's path filters currently omit flake.nix and
the VM tests and mistakenly reference .github/workflows/build.yaml, so PRs that
only change the flake or test/vm/ won't trigger the test workflow; update the
on: paths/pull_request (and push if present) in .github/workflows/test.yaml to
include "flake.nix" and "test/vm/**" (or "test/vm/") and correct the referenced
workflow path from ".github/workflows/build.yaml" to
".github/workflows/test.yaml" so the NH VM test step (the "Test NH features in
NixOS VM" step that builds .#checks.x86_64-linux.nh-remote-test) runs for
relevant changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 18f77d44-14fe-4b2f-9bc7-592549b491ef

📥 Commits

Reviewing files that changed from the base of the PR and between 1d28d72 and 9daeed9.

📒 Files selected for processing (1)
  • .github/workflows/test.yaml

Comment on lines +49 to +51
- name: Test NH features in NixOS VM
run: |
nix build .#checks.x86_64-linux.nh-remote-test
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Show whether this workflow is triggered by VM/flake/test workflow changes.

sed -n '3,24p' .github/workflows/test.yaml

printf '\nExpected these paths to be present for the new VM check:\n'
rg -n 'flake\.nix|flake\.lock|test/vm|\.github/workflows/test\.yaml' .github/workflows/test.yaml || true

Repository: nix-community/nh

Length of output: 601


Include flake and VM test paths in workflow triggers so the new test actually runs.

The new step at line 51 depends on flake.nix and test/vm/ changes, but the workflow's path filters omit them. PRs that only modify the flake check or VM tests will skip this workflow entirely. Additionally, the path filter currently references .github/workflows/build.yaml but should reference .github/workflows/test.yaml.

Path-filter update needed
 on:
   workflow_dispatch:
   pull_request:
     paths:
       - "Cargo.toml"
       - "Cargo.lock"
+      - "flake.nix"
+      - "flake.lock"
       - "xtask/Cargo.toml"
       - "crates/**"
+      - "test/vm/**"
       - "xtask/src/**"
       - ".cargo/**"
-      - ".github/workflows/build.yaml"
+      - ".github/workflows/test.yaml"
   push:
     branches-ignore:
       - "update-*"
     paths:
       - "Cargo.toml"
       - "Cargo.lock"
+      - "flake.nix"
+      - "flake.lock"
       - "xtask/Cargo.toml"
       - "crates/**"
+      - "test/vm/**"
       - "xtask/src/**"
       - ".cargo/**"
-      - ".github/workflows/build.yaml"
+      - ".github/workflows/test.yaml"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/test.yaml around lines 49 - 51, The workflow's path
filters currently omit flake.nix and the VM tests and mistakenly reference
.github/workflows/build.yaml, so PRs that only change the flake or test/vm/
won't trigger the test workflow; update the on: paths/pull_request (and push if
present) in .github/workflows/test.yaml to include "flake.nix" and "test/vm/**"
(or "test/vm/") and correct the referenced workflow path from
".github/workflows/build.yaml" to ".github/workflows/test.yaml" so the NH VM
test step (the "Test NH features in NixOS VM" step that builds
.#checks.x86_64-linux.nh-remote-test) runs for relevant changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant